package com.huawei.cloud.phone.management.infrastructure;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.huawei.cloud.phone.management.common.utils.JsonUtils;
import com.huawei.cloud.phone.management.domain.pkg.PkgResult;
import com.huaweicloud.sdk.core.SdkResponse;
import com.huaweicloud.sdk.cph.v1.CphClient;
import com.huaweicloud.sdk.cph.v1.model.Job;
import com.huaweicloud.sdk.cph.v1.model.ListJobsRequest;
import com.huaweicloud.sdk.cph.v1.model.ListJobsResponse;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.stream.Collectors;

@Slf4j
public class CphClientInvoker<I, O extends SdkResponse> {
    private static final int HTTP_OK = 200;

    private static final int JOB_RUNNING = 1;

    private static final int JOB_FAILED = -1;

    /**
     * 调用CPH的SDK接口，并处理返回值
     *
     * @param in       SDK参数
     * @param function 调用的SDK接口
     * @return 处理过的返回值
     */
    public List<PkgResult> invoke(I in, BiFunction<CphClient, I, O> function) {
        log.info("invoke request {}", in);
        CphClient client = ClientFactory.getCphClient();
        O out = function.apply(client, in);
        log.info("invoke response {}", out);
        CommonSdkResponse commonResponse = JsonUtils.parseData(JsonUtils.toJsonString(out), CommonSdkResponse.class);
        commonResponse.setHttpStatusCode(out.getHttpStatusCode());
        if (commonResponse.getHttpStatusCode() != HTTP_OK) {
            return genResult(commonResponse.getJobs());
        }
        return checkRequest(client, commonResponse);
    }

    private List<PkgResult> genResult(List<CommonJob> jobs) {
        return Optional.ofNullable(jobs).orElse(Collections.emptyList())
                .stream().filter(job -> Objects.nonNull(job.getErrorCode()))
                .map(job -> new PkgResult(job.getKey(), job.getErrorCode(), job.getErrorMsg()))
                .collect(Collectors.toList());
    }

    private List<PkgResult> checkRequest(CphClient client, CommonSdkResponse commonResponse) {
        String requestId = commonResponse.getRequestId();
        log.info("start check request {}", requestId);
        ListJobsResponse response = waitJobFinish(client, requestId);
        List<Job> jobs = filterFailedJob(response);
        Map<String, String> map = commonResponse.getJobs().stream()
                .collect(Collectors.toMap(CommonJob::getJobId, CommonJob::getKey));
        return convertResult(jobs, map);
    }

    private ListJobsResponse waitJobFinish(CphClient client, String requestId) {
        while (true) {
            boolean needWaiting = false;
            log.info("check request {}", requestId);
            ListJobsRequest request = new ListJobsRequest();
            request.setRequestId(requestId);
            ListJobsResponse response = client.listJobs(request);
            List<Job> jobs = response.getJobs();
            for (Job job : jobs) {
                // 存在运行中的任务
                if (job.getStatus() == JOB_RUNNING) {
                    sleep();
                    needWaiting = true;
                    break;
                }
            }
            if (!needWaiting) {
                return response;
            }
        }
    }

    private void sleep() {
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            log.error("sleep failed", e);
        }
    }

    private List<Job> filterFailedJob(ListJobsResponse response) {
        return response.getJobs().stream()
                .filter(job -> job.getStatus() == JOB_FAILED)
                .peek(job -> log.error("job failed {}", job))
                .collect(Collectors.toList());
    }

    private List<PkgResult> convertResult(List<Job> jobs, Map<String, String> jobIdMap) {
        return jobs.stream()
                .map(job -> new PkgResult(jobIdMap.get(job.getJobId()), job.getErrorCode(), job.getErrorMsg()))
                .collect(Collectors.toList());
    }

    @Data
    @ToString(callSuper = true)
    @EqualsAndHashCode(callSuper = true)
    @JsonInclude(JsonInclude.Include.NON_NULL)
    private static class CommonSdkResponse extends SdkResponse {
        @JsonProperty(value = "request_id")
        private String requestId;

        @JsonProperty(value = "jobs")
        private List<CommonJob> jobs = null;
    }

    @Data
    @JsonInclude(JsonInclude.Include.NON_NULL)
    private static class CommonJob {
        @JsonProperty(value = "server_id")
        private String serverId;

        @JsonProperty(value = "phone_id")
        private String phoneId;

        @JsonProperty(value = "job_id")
        private String jobId;

        @JsonProperty(value = "error_code")
        private String errorCode;

        @JsonProperty(value = "error_msg")
        private String errorMsg;

        public String getKey() {
            return Objects.nonNull(serverId) ? serverId : phoneId;
        }
    }
}
